using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
using MediaPortal.GUI.Library;
using MediaPortal.InputDevices;
using SevenZip;
using System.Windows.Forms;
using MediaPortal.Util;

namespace MyEmulators2
{
    public delegate void ExecutorStatusChangedEventHandler(bool isRunning);

    public class Executor : IDisposable
    {
        #region Singleton

        static Executor instance = null;
        public static Executor Instance
        {
            get
            {
                if (instance == null)
                    instance = new Executor();
                return instance;
            }
        }

        #endregion

        //Handles the parsing of the commands and executes the game
        private const String gameWildcard = "%ROM%";
        private const String gameWithoutExtWildcard = "%ROM_WITHOUT_EXT%";

        EmulatorProfile currentProfile = null;
        bool isMounted = false;

        KeyboardHook hook = null;
        KeyEventHandler keyEvent = null;
        Keys key = Keys.None;

        Process proc = null;

        public event ExecutorStatusChangedEventHandler StatusChanged; 

        public static void launchDocument(DBItem item)
        {
            string manualPath = null;
            using (ThumbGroup thumbGroup = new ThumbGroup(item))
                manualPath = thumbGroup.ManualPath;

            if (string.IsNullOrEmpty(manualPath))
                return;

            //Execute
            using (Process proc = new Process())
            {
                proc.StartInfo = new ProcessStartInfo();
                proc.StartInfo.FileName = manualPath;
                proc.Start();
            }
        }

        public void launchGame(Game game)
        {
            launchGame(game, false, null);
        }

        public void launchGame(Game game, bool isConfig)
        {
            launchGame(game, isConfig, null);
        }

        public void launchGame(Game game, EmulatorProfile profile)
        {
            launchGame(game, false, profile);
        }

        /// <summary>
        /// Launches the specified game using the specified profile. If the specified profile is null
        /// the game's configured profile is used. If isConfig is true then any suspend settings are ignored
        /// and the specified game's playcount and playdate are not updated.
        /// </summary>
        /// <param name="game"></param>
        /// <param name="isConfig">Whether the game is being launched by Configuration. Default false.</param>
        /// <param name="profile">The profile to use when launching the game. Default null (use game's configured profile).</param>
        public void launchGame(Game game, bool isConfig, EmulatorProfile profile)
        {
            //stop any media playing
            if (Options.Instance.GetBoolOption("stopmediaplayback") && MediaPortal.Player.g_Player.Playing)
                MediaPortal.Player.g_Player.Stop();

            //get required profile
            if (profile == null || profile.EmulatorID != game.ParentEmulator.UID)
                profile = game.GetSelectedProfile(); //profile is invalid, load configured profile

            //only suspend if we're not in config and either suspendMP is true or game is a PC game
            bool suspend = !isConfig && (profile.SuspendMP == true || game.ParentEmulator.isPc());

            //if we're suspending create handler to resume on game exit, else create default handler
            EventHandler exitEvent = suspend ? new EventHandler(onGameExit) : new EventHandler(defaultOnGameExit);

            string path = "";
            string args = "";
            string workingDir = "";
            isMounted = false;

            if (game.ParentEmulator.isPc()) //PC Game
            {
                path = game.Path;

                //remove any args after file name and add to args param 
                if (!path.EndsWith(".exe") && !path.EndsWith(".bat"))
                {
                    int index = path.LastIndexOf('.');
                    if (index > -1)
                    {
                        args += path.Substring(index + 5);
                        path = path.Remove(index + 4);
                    }
                }
                workingDir = Directory.GetParent(game.Path).FullName;
            }
            else //Emu game
            {
                path = profile.EmulatorPath;

                args = getEmuArgs(game, profile); 
                if (args == null) //problem with emu config
                    return;

                //if workingdir has been configured use it
                if (Directory.Exists(profile.WorkingDirectory))
                    workingDir = profile.WorkingDirectory;
                else if (File.Exists(profile.EmulatorPath))
                    workingDir = Directory.GetParent(profile.EmulatorPath).FullName;
            }

            string emulatorErrorTxt = game.ParentEmulator.isPc() ? "" : " Emulator";
            if (string.IsNullOrEmpty(path))
            {
                Logger.LogError("Unable to launch {0}, no{1} executable has been configured", game.Title, emulatorErrorTxt);
                Conf_GlobalSettings.Instance.ShowMPDialog("Unable to launch game:\r\nNo{0} executable has been configured", emulatorErrorTxt);
                return;
            }
            else if (!File.Exists(path))
            {
                Logger.LogError("Unable to launch {0}, could not locate{1} executable '{2}'", game.Title, emulatorErrorTxt, path);
                Conf_GlobalSettings.Instance.ShowMPDialog("Unable to launch game:\r\nCould not locate{0} executable\r\n'{1}'", emulatorErrorTxt, path);
                return;
            }

            if (!isConfig)
            {
                //Update statistics if not config
                game.Latestplay = DateTime.Now;
                game.Playcount++;
                game.SaveGamePlayInfo();
            }

            disposeProc();

            currentProfile = profile;

            //Execute
            proc = new Process();
            proc.StartInfo = new ProcessStartInfo();
            proc.StartInfo.FileName = path;
            proc.StartInfo.Arguments = args;
            proc.StartInfo.WorkingDirectory = workingDir;
            proc.EnableRaisingEvents = true;
            proc.Exited += exitEvent;

            Logger.LogInfo("Launching {0} with arguments '{1}'", game.ParentEmulator.Title, args);

            if (!proc.Start())
            {
                Logger.LogError("Executor - Process did not start");
                disposeProc();
                return;
            }

            //send status changed event
            if (StatusChanged != null)
                StatusChanged(true);

            //setup keyboard hook if configured and we're not launching a PC Game (they should have their own way of exiting)
            if (!game.ParentEmulator.isPc() && Options.Instance.GetBoolOption("domap"))
                setupKeyboardHook();

            if (suspend)
                suspendMP(true); //suspend MediaPortal rendering
        }

        //get key mapping and setup keyboard hook
        void setupKeyboardHook()
        {
            string keyMap = Options.Instance.GetStringOption("mappedkey");
            if (String.IsNullOrEmpty(keyMap))
            {
                Logger.LogError("Keyboard hook - No key has been mapped");
                return;
            }

            Logger.LogInfo("Stop emulation on mapped key selected. Initialising keyboard hook...");

            key = Keys.None;
            try
            {
                key = (Keys)Enum.Parse(typeof(Keys), keyMap, true); //attempt to parse Keys enum from option string
            }
            catch
            {
                key = Keys.None;
                Logger.LogError("Keyboard hook - Error parsing key '{0}'", keyMap);
            }

            if (key != Keys.None) //enum parse was successful
            {
                Logger.LogDebug("Keyboard hook - Emulation will be stopped on '{0}' key", keyMap);
                keyEvent = new KeyEventHandler(onMappedKey);
                disposeHook();
                hook = new KeyboardHook(proc.Id, keyEvent); //setup hook and attach to emu process
            }
        }

        //Fired when a key press is detected by the keyboard hook
        void onMappedKey(object sender, KeyEventArgs e)
        {
            if (e.KeyData == key) //key pressed is mapped key
            {
                Logger.LogDebug("Keyboard hook - Mapped key pressed, stopping emulation");
                e.Handled = true;
                e.SuppressKeyPress = true;

                int Msg;
                uint wParam;
                if (currentProfile != null && currentProfile.EscapeToExit)
                {
                    //set message to Esc key press
                    Msg = KeyboardHook.WM_KEYDOWN;
                    wParam = KeyboardHook.VK_ESCAPE;
                }
                else
                {
                    //Set message to window close
                    Msg = KeyboardHook.WM_QUIT;
                    wParam = 0;
                }
                IntPtr wH = proc.MainWindowHandle;
                if (wH != IntPtr.Zero)
                {
                    try
                    {
                        KeyboardHook.PostMessage(wH, Msg, wParam, 0);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError("Keyboard hook - error sending close message to emulator - {0}", ex.Message);
                    }
                }
            }
        }

        void disposeHook()
        {
            if (hook != null)
            {
                Logger.LogDebug("Keyboard hook - Disposing...");
                hook.Dispose();
                hook = null;
            }
        }

        void disposeProc()
        {
            if (proc != null)
            {
                proc.Dispose();
                proc = null;
            }
        }

        void defaultOnGameExit(object obj, EventArgs e)
        {
            disposeHook();
            disposeProc();
            unMount();

            if (StatusChanged != null)
                StatusChanged(false);
        }

        void onGameExit(object obj, EventArgs e)
        {
            //resume Mediaportal rendering
            suspendMP(false);
            defaultOnGameExit(obj, e);
        }

        string getEmuArgs(Game game, EmulatorProfile emuProfile)
        {
            
            string args = emuProfile.Arguments;
            string romPath = game.Path;

            //if Goodmerge is enabled and rom has a valid extension, extract
            if (game.ParentEmulator.EnableGoodmerge && game.IsGoodmerge)
                romPath = Extractor.Instance.ExtractGame(game, emuProfile);

            if (romPath == null) //error extracting game
                return null;

            string ext = "";
            int index = romPath.LastIndexOf('.');
            if (index > -1)
                ext = romPath.Substring(index);

            if (emuProfile.MountImages && DaemonTools.IsImageFile(ext))
            {
                Logger.LogInfo("Attempting to mount image file '{0}'", romPath);
                if (DaemonTools.IsEnabled)
                {
                    string drive;
                    DaemonTools.UnMount();
                    DaemonTools.Mount(romPath, out drive);
                    isMounted = true;
                    return args;
                }
                else
                {
                    Logger.LogError("Attempt to mount image file failed, Mediaportal's Virtual Drive is not enabled");
                    Conf_GlobalSettings.Instance.ShowMPDialog("Unable to mount disk image:\r\nPlease enable Mediaportal's Virtual Drive");
                    return null;
                }
            }

            //To allow the use of arguments after the game file, use gameWildcard
            if (!args.Contains(gameWildcard) && !args.Contains(gameWithoutExtWildcard))
            {
                if (args.Length > 0)
                {
                    args += " ";
                }
                args += gameWildcard;
            }

            if (emuProfile.UseQuotes == true)
            {
                args = args.Replace(gameWildcard, "\"" + romPath + "\"");
            }
            else
            {
                args = args.Replace(gameWildcard, romPath);
            }

            //To have the filename without the path and extension, use gameWithoutExtWildcard
            if (args.Contains(gameWithoutExtWildcard))
            {
                string replacer = romPath.Remove(romPath.LastIndexOf('.'));
                args = args.Replace(gameWithoutExtWildcard, replacer.Substring(replacer.LastIndexOf('\\') + 1));
            }

            return args;
        }

        void unMount()
        {
            if (isMounted && DaemonTools.IsEnabled)
            {
                DaemonTools.UnMount();
                isMounted = false;
            }
        }

        /* The following code was referenced from the Moving Pictures plugin */
        static void suspendMP(bool suspend)
        {
            if (suspend) //suspend and hide MediaPortal
            {
                Logger.LogDebug("Suspending MediaPortal...");
                // disable mediaportal input devices
                InputDevices.Stop();

                // hide mediaportal and suspend rendering to save resources for the pc game
                GUIGraphicsContext.BlankScreen = true;
                GUIGraphicsContext.form.Hide();
                GUIGraphicsContext.CurrentState = GUIGraphicsContext.State.SUSPENDING;
            }
            else //resume Mediaportal
            {
                Logger.LogDebug("Resuming MediaPortal...");

                InputDevices.Init();
                // Resume Mediaportal rendering
                GUIGraphicsContext.BlankScreen = false;
                GUIGraphicsContext.form.Show();
                GUIGraphicsContext.ResetLastActivity();
                GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_GETFOCUS, 0, 0, 0, 0, 0, null);
                GUIWindowManager.SendThreadMessage(msg);
                GUIGraphicsContext.CurrentState = GUIGraphicsContext.State.RUNNING;
            }
        }

        public void Dispose()
        {
            disposeHook();
            disposeProc();
        }
    }
}
